"""
Helper functions for hdf tables

>>> from mod454.schema import Mod454Schema as Schema
>>>
>>> # generates a filename
>>> fname = atlas.testdata('test-database.hdf', verify=False)
>>>
>>> # opens the datbase
>>> db = hdf_open(fname, mode='w', title='Test database')
>>>
>>> # create a table
>>> table = create_table( db=db, name='foo', schema=Schema, clobber=True )
>>> 
>>> # getting the same table by name
>>> other = get_node( db=db, name='foo' )
>>> table == other
True
>>> has_node( db=db, name='foo' )
True
>>>  
>>> # create a group foo and tables bar-1, bar-2 in the group
>>> group = create_group( db=db, name='foo', clobber=True )
>>> bar1 = create_table( db=db, name='bar1', where=group, schema=Schema, clobber=True )
>>> bar2 = create_table( db=db, name='bar2', where=group, schema=Schema, clobber=True )
>>>
>>> # add some data to the table
>>> for i in range(50):
...     row = ( 2*i, 100, 200, 300 ) 
...     bar2.append( [ row ] )
>>> bar2.flush()
>>> len(bar2)
50
>>>
>>> # the GroupData class is a wrapper around a group and tables
>>> gdata = GroupData( db=db, name='foo')
>>> gdata.labels
['bar1', 'bar2']
>>> gdata.get_table('bar2').cols.ix[:10]
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
>>> gdata.get_table('bar2').cols.wx[:5]
array([ 100.,  100.,  100.,  100.,  100.])
>>> gdata.get_table('bar2').cols.cx[:5]
array([ 200.,  200.,  200.,  200.,  200.])
>>> gdata.get_table('bar2').cols.ax[:5]
array([ 300.,  300.,  300.,  300.,  300.])
>>> gdata.get_indices(label='bar2', start=2, stop=10)
(1, 5)
>>> db.close()

$Rev: 252 $ 
$Author: ialbert $ 
$Date: 2008-02-13 09:44:16 -0500 (Wed, 13 Feb 2008) $
"""
import atlas
import os, bisect
from tables import *
from itertools import izip, count

try:
    # monkeypatch pytables to disable the Natural Name warning
    import re
    from tables import path
    path._pythonIdRE = re.compile('.')
except:
    atlas.info( 'could not patch the Natural Name warning' )

class GroupData(object):
    "Represents a group of data under different labels (for example chromosomes in the genome)"
    def __init__ (self, db, name, where='/'):
        self.group  = get_node( db=db, where=where, name=name)
        assert self.group is not None, 'group %s was not found' % name
        self.labels = [ x.name for x in self.group._f_listNodes() ]
        atlas.nice_sort( self.labels )
    
    def get_table(self, label):
        "Returns the hdf table associated with the label"
        assert label in self.labels, "label '%s' was not found" % label
        return getattr( self.group, label )

    def get_indices( self, label, start, stop, colattr='ix'):
        "Returns the iterator that stars at the start, stop *indices*"
        table = self.get_table(label)
        istart, istop = index_search(args=[start, stop], table=table, colattr=colattr)
        return istart, istop

    def intervals(self, label, size=10):
        """
        Returns the intervals that will cover the table with a specified 
        step size
        """
        limit = self.get_table(label)[-1][0]
        for index, start in izip(count(), xrange(0, limit, size)):
            yield index, start, start+size

def index_search(args, table, colattr='ix'):
    """
    Returns indices as list for for all the arguments.
    """
    def search(value):
        return binary_search(value=value, table=table, colattr='ix')
    return map( search, args)

def binary_search( value, table, colattr='ix'):
    """
    Returns the index of the element left of the value. 
    
    Note that for this to work the values for the column attribute 'colattr' 
    in the table must be sorted in increasing order     
    """
    column = getattr(table.cols, colattr)
    index  = bisect.bisect_left( column, value )
    return index

def hdf_open( fname='default.hdf', mode='a', title='HDF database' ):
    """
    Returns the HDF database handle from the file name.
    """

    # verify that the base directory exists
    dirpath, filename = os.path.split( fname )
    if dirpath:
        atlas.isdir( path=dirpath )

    if mode == 'w':
        atlas.debug('creating HDF=%s' % fname)
    else:
        
        atlas.debug('opening HDF=%s' % fname)
        
    return openFile( fname, mode=mode, title=title )

def has_node(db, where='/', name=None):
    """
    Tests nodes for existence
    """
    return get_node(db=db, where=where, name=name) is not None

def get_node(db, where='/', name=None):
    """
    Returns the node or None if the node does not exist.
    """
    try:
        return db.getNode(where=where, name=name)
    except NoSuchNodeError:
        return None

def remove_node(db, name, where='/'):
    """
    Removes a node if exist
    """
    if has_node(db=db, name=name, where=where):
        db.removeNode(where=where, name=name, recursive=True)
        text = as_string( name )
        atlas.debug( "removed node %s%s" % (where, text) )

def as_string( node ):
    """
    Returns string description for a node path
    """
    if type(node) != str:
        try:
            text = '%s' % node._v_hdf5name
        except NoSuchNodeError:
            text = '%s' % node._v_name
    else:
        text = node
    
    return text

def create_table(db, name, schema, desc="New table", where="/", clobber=False):
    """
    Creates a table with the given class. If 'clobber' is true it 
    will remove the node first
    """    
    if clobber:
        remove_node( db=db, where=where, name=name)
    table = db.createTable( where, name, schema, desc )
    wtext = as_string( where )
    atlas.debug( 'created table where=%s, name=%s' % (wtext, name) )
    return table

def create_group(db, name, desc='New group', where="/", clobber=False):
    """
    Creates a group in the database. If 'clobber' is true it 
    will remove the node first
    """
    if clobber:
        remove_node( db=db, where=where, name=name)
    group = db.createGroup( where, name, desc)
    atlas.debug( 'created group where=%s, name=%s' % (where, name) )
    return group

def test(verbose=0):
    """
    Main testrunnner
    """
    import doctest
    doctest.testmod( optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE, verbose=verbose )

if __name__ == '__main__':
    test(verbose=0)
